package comm;

import gfx.tiles.Tile;
import gfx.tiles.TileRenderer;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Random;

import objects.Inventory;
import objects.Robot;
import core.Command;
import core.Main;
import core.Vector2;

/**
 * 
 * @author ruben.ortlam
 *
 */
public class Server {
	
	private ServerSocket server;
	public java.util.List<Client> clients;
	private Thread clientListenerThread;
	private ClientListener clientListener;
	
	/**
	 * Creates a Server for multiple Clients.
	 */
	public Server() {
			try {
				server = new ServerSocket(7777);
			} catch (IOException e) {
				e.printStackTrace();
			}
			System.out.println("Initialized Server on Port " + server.getLocalPort());
			
			clients = new java.util.ArrayList<Client>();
			clientListener = new ClientListener();
			clientListenerThread = new Thread(clientListener);
			clientListenerThread.start();
			
			run();
	}
	/**
	 * Starts the loop listening for messages from clients and evaluating them.
	 */
	private void run() {
		for(;;) {
//			System.out.println(clients.size());
			for(int i = clients.size() - 1; i >= 0; i--) {
				
				if(clients.get(i).isIdentified()) {
					synchronized(this) {
						if(clients.get(i).isDisconnected()) {
							System.out.println("Client " + i + " disconnected!");
							
							if(clients.get(i).robot != null) {
								Main.removeDrawable(clients.get(i).robot);
								Main.removeTickable(clients.get(i).robot);
							}
							
							try {
								clients.get(i).socket.shutdownOutput();
								clients.get(i).socket.shutdownInput();
								clients.get(i).socket.close();
							} catch (IOException e) {
								e.printStackTrace();
							}
							
							try {
								clients.get(i).inThread.join(10);
							} catch (InterruptedException e1) {
								e1.printStackTrace();
							}
							
							clients.get(i).inThread = null;
							clients.remove(i);
							continue;
						}
						
						if(clients.get(i).robot == null/* && 
			    				System.currentTimeMillis() - clients.get(i).lastRespawn >= 5000*/) {
			    			Random random = new Random();
			    			
			    			Vector2 tmp = new Vector2(random.nextInt(Main.worldWidth * Tile.size), random.nextInt(Main.worldHeight * Tile.size));
			    			
			    			while(TileRenderer.tileCollision(tmp, Robot.SIZE) != Tile.AIR) {
			    				tmp = new Vector2(random.nextInt(Main.worldWidth * Tile.size), random.nextInt(Main.worldHeight * Tile.size));
			    			}
			    			
					    	Robot robot = new Robot(tmp, random.nextDouble() * 2 * Math.PI, 
					    			clients.get(i).preferredColor, 
					    			clients.get(i).preferredName, 
					    			clients.get(i));
							
							Main.addDrawable(robot);
							Main.addTickable(robot);
							Main.mode.addRobot(robot);
							clients.get(i).robot = robot;
							clients.get(i).lastRespawn = System.currentTimeMillis();
							System.out.println("Added Robot");
			    		}
						
						int in = -1;
						
						if(clients.get(i).in.isReady()) {
							in = clients.get(i).in.getInput();
							
							System.out.println("IN" + i + ": " + in);
						}
						
						if(in != -1) {
							int type = in & 0xF;
							System.out.println("TYPE: " + type);
							
							switch(type) {
							case 0:
								int tmp = in >> 4;
								System.out.println("tmp: " + tmp + "; tmp & 0xF: " + (tmp & 0xF));
								
								switch(tmp & 0xF) {
								case 0:
									clients.get(i).robot.clearCommands();
									break;
								default:
									Command cmd = new Command(tmp & 0xF, tmp >> 4, clients.get(i).robot);
									System.out.println("tmp >> 4: " + (tmp >> 4));
									clients.get(i).robot.addCommand(cmd);
									break;
								}
								break;
							case 1:
								clients.get(i).lastPing = System.currentTimeMillis();
		//						System.err.println("PING");
								break;
							case 2:
								int tmp2 = in >> 4;
								System.out.println("Stmp: " + tmp2 + "; tmp & 0xF: " + (tmp2 & 0xF));
								
								switch(tmp2 & 0xF) {
								case 0:
									clients.get(i).robot.clearCommands();
									break;
								default:
									switch(tmp2 >> 4) {
									case 0:
										double[] temp2 = clients.get(i).robot.getSensorDistance();
										if(temp2 != null) {
											Client client = clients.get(i);
											
											int temp3 = (int)Math.round(temp2[0]);
											
											temp3 <<= 12;
											temp3 += (int)Math.round(Math.toDegrees(temp2[1]));
											temp3 <<= 8;
											
											try {
												client.out.writeInt(temp3);
											} catch (IOException e) {
												e.printStackTrace();
											}
										}
										break;
									case 1:
										int temp4 = clients.get(i).robot.getGroundSensorData();
										
										temp4 <<= 4;
										temp4 += 1;
										temp4 <<= 4;
										
										try {
											clients.get(i).out.writeInt(temp4);
										} catch (IOException e) {
											e.printStackTrace();
										}
										break;
									case 2:
										int size = clients.get(i).robot.getInventory().SIZE;
										
										int buffer = 0;
										
										//try {
											buffer = clients.get(i).robot.getInventory().createInventoryReport(Inventory.INVENTORY_BEGIN, 0);
											System.out.println(Integer.toBinaryString(buffer));
											//clients.get(i).out.writeInt(buffer);
											
											for (int item = 0; i < clients.get(i).robot.getInventory().SIZE; i++) {
												buffer = clients.get(i).robot.getInventory().createInventoryReport(Inventory.INVENTORY_ITEM, item);
												System.out.println(Integer.toBinaryString(buffer));
												//clients.get(i).out.writeInt(buffer);
											}
											
											buffer = clients.get(i).robot.getInventory().createInventoryReport(Inventory.INVENTORY_END, 0);
											System.out.println(Integer.toBinaryString(buffer));
											//clients.get(i).out.writeInt(buffer);
										/*
										} catch (IOException e) {
											e.printStackTrace();
										}
										*/
										break;
									}
									
									break;
								}
								break;
							}
						}
					}
				}
			}
			
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
    
    /**
	 * Returns the client that controls the robot.
	 *
	 * @param robot The robot the client controls.
	 * @return The Client controlling the robot, null if no client is found controlling this robot.
	 */
	public Client getClientOfRobot(Robot robot) {
		for(int i = 0; i < clients.size(); i++) {
			if(clients.get(i).robot == robot) {
				return clients.get(i);
			}
		}
		return null;
	}
	
	/**
	 * A class to create a thread that listens for new incoming clients.
	 */
	class ClientListener implements Runnable {
		
		/**
		 * Listens to clients and adds them to the clients list.
		 */
		@Override
		public void run() {
			for(;;) {
				try {
					Socket socket = server.accept();
					if(socket != null) {
						System.out.println("Client Number " + clients.size() + " connected from IP: " + socket.getLocalAddress().toString());
						clients.add(new Client(socket));
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
